In the latter half of the twentieth century, there began a phenomenon known as the information revolution. While the information revolution is a historical development broader in scope than any one event or machine, no single device has come to represent the information revolution more than the digital electronic computer. The development of computer systems has surely been a revolution. Each year, computer systems grow faster, store more data, and provide more applications to their users.
A modern computer system typically comprises one or more central processing units (CPU) and supporting hardware necessary to store, retrieve and transfer information, such as communication buses and memory. It also includes hardware necessary to communicate with the outside world, such as input/output controllers or storage controllers, and devices attached thereto such as keyboards, monitors, tape drives, disk drives, communication lines coupled to a network, etc. The CPU or CPUs are the heart of the system. They execute the instructions which comprise a computer program and direct the operation of the other system components.
From the standpoint of the computer's hardware, most systems operate in fundamentally the same manner. Processors are capable of performing a limited set of very simple operations, such as arithmetic, logical comparisons, and movement of data from one location to another. But each operation is performed very quickly. Sophisticated software at multiple levels directs a computer to perform massive numbers of these simple operations, enabling the computer to perform complex tasks. What is perceived by the user as a new or improved capability of a computer system is made possible by performing essentially the same set of very simple operations, but using software having enhanced function, along with faster hardware.
In the very early history of the digital computer, computer programs which instructed the computer to perform some task were written in a form directly executable by the computer's processor. Such programs were very difficult for a human to write, understand and maintain, even when performing relatively simple tasks. As the number and complexity of such programs grew, this method became clearly unworkable. As a result, alternate forms of creating and executing computer software were developed.
The evolution of computer software has led to the creation of sophisticated software development environments. These environments typically contain a range of tools for supporting the development of software in one or more high-level languages. For example, interactive source editors support the initial generation of source code by a developer. Source databases may support collections of source modules or source objects, which serve as the component parts of software applications. Front-end compiler/debuggers perform simple semantic verification of the source and reduction to a standard form. Back-end or optimizing compilers generate machine executable object code from the standard form, and may optimize the performance of this code using any of various optimization techniques. Build utilities assemble multiple object code modules into fully functioning computer programs.
Among the tools available in many such programming development environments are a range of diagnostic and debug tools. Although source editors and compilers used during the initial creation and compilation phases of development can identify certain obvious inconsistencies in source code and produce object code conforming to the source, they can not necessarily verify more subtle flaws in the logic of a program itself, or determine that the program makes use of available resources in an efficient manner. This is generally accomplished by observing the behavior of the program at “run-time”, i.e., when executed under real or simulated input conditions. Various trace tools exist which collect data concerning the run-time behavior of a computer program. Such tools may optionally trigger break points in the programming code, at which execution of the program is suspended and machine state can be examined. Analytical debug tools assist the programmer in analyzing the trace data after the fact, or machine state during program suspension, to find logical errors, inefficiencies, or other problems with the code.
When analyzing trace data or machine state, the programmer generally needs to associate executable object code with source code. I.e., the programmer will want to know which executable instruction or instructions correspond to some particular source code statement, which memory locations correspond to key variables in the source code, the location of stacks corresponding to called procedures, and so forth. Data necessary for making these associations is typically generated in the compilation process. E.g., compilation listings can be generated which correlate executable instructions with the source code statements upon which they are based. Program traces of dynamically allocated memory spaces can similarly be related back to the executable instructions, and the source code statements, causing particular actions.
For optimum analytical and debugging capability, it is preferable to have a simple, direct correspondence between object code and source code. I.e., the easiest object code to analyze using conventional debug tools is code such that for every source code statement, there is a discrete, contiguous sequences of executable instructions in the object code which correspond to it. Furthermore, debug analysis is facilitated if each segment of executable instructions corresponding to a source code statement writes any affected variables to memory, for this guarantees that the debug tools can find the current value of the variables in their respective memory locations.
Unfortunately, what is preferable from the standpoint of analytical and debugging capability (“serviceability”) is not always preferable from the standpoint of optimum execution performance. In order to produce code which executes in as efficient a manner as possible, so-called “optimizing compilers” are frequently used to generate the executable object code from source. An optimizing or other compiler may produce performance optimizations, such as (a) changing the order of operations; (b) leaving frequently-used variables in registers without loading and dumping their values to memory; (c) holding partial mathematical expressions in registers for use in later instructions; (d) removing procedure calls by in-lining; (e) removing unused code branches and branch statements; and various others. As a result of performance optimizations performed by an optimizing compiler, the resulting object code may not have the same correspondence to the source code. In fact, it may bear almost no resemblance to the source, and the object code instructions corresponding to any particular source code statement may be non-contiguous, non-recognizable, or even non-existent. As a result, the capability to analyze and debug executable code generated by an optimizing compiler is often very limited.
Because it is known that executable code produced by an optimizing compiler is difficult to analyze and debug, program developers typically produce non-optimized code during the initial development process, and render the code in optimized form at a later stage in the process, when it is believed that most of the defects have been cured. For this purpose, various conventional optimizing compilers have the capability to turn optimizing off or on at the program developer's option. But despite best efforts of the developers, there will be some defects which manifest themselves only after the code has been distributed to end users and has been operating in the end users' environments. Because this code is typically generated by an optimizing compiler, analysis of problems occurring in the end user's environment is difficult.
It would, of course, be possible to simply provide the end user with code which has not been subjected to optimizing techniques, but this is likely to result in noticeably worse execution performance, to the point where it may affect the acceptability of the product from the point of view of the end user.
As programs grow in size and complexity, the task of analyzing and debugging programming code, and particularly servicing programming code after installation in the end user's environment, becomes increasingly difficult. A need exists for improved methods and techniques for servicing computer programming code, without unduly degrading execution performance.